# coding=utf-8
import commands
import ctypes
import functools
import inspect
import json
import logging
import os
import pickle
import threading
import time
import glob
from datetime import date, datetime
from shutil import copyfile

import re

from config import BeetleConfig

_log = logging.getLogger(__name__)


def serialize(obj):
    return pickle.dumps(obj=obj, protocol=0)


def deserialize(str):
    return pickle.loads(str=str)


def path_modify_time(path):
    if os.path.exists(path):
        if os.path.isdir(path):
            for filename in os.listdir(path):
                file_path = path + '/' + filename
                if os.path.isfile(file_path):
                    stat = os.stat(file_path)
                    return time.localtime(stat.st_mtime)

            return None
        elif os.path.isfile(path):
            stat = os.stat(path)
            return time.localtime(stat.st_mtime)
        else:
            return None

    else:
        return None


class JsonEncoder:
    def __init__(self):
        pass


class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(obj, date):
            return obj.strftime('%Y-%m-%d')
        else:
            return json.JSONEncoder.default(self, obj)


class Converter:
    def __init__(self):
        pass

    @classmethod
    def typeof_list_or_set(cls, obj):
        is_list = obj.__class__ == [].__class__
        is_set = obj.__class__ == set().__class__
        return is_list or is_set

    @classmethod
    def typeof_dict(cls, obj):
        is_dicts = obj.__class__ == {}.__class__
        return is_dicts

    @classmethod
    def obj_to_dict(cls, obj):

        if Converter.typeof_list_or_set(obj):
            obj_arr = []
            for o in obj:
                obj_arr.append(Converter.obj_to_dict(o))
            return obj_arr
        elif isinstance(obj, JsonEncoder):
            data = obj.__dict__
            return Converter.obj_to_dict(data)
        elif Converter.typeof_dict(obj):
            for key, val in obj.items():
                print key, val
                if isinstance(val, JsonEncoder):
                    obj[key] = Converter.obj_to_dict(val)
                elif Converter.typeof_list_or_set(val):
                    obj[key] = Converter.obj_to_dict(val)
                elif Converter.typeof_dict(val):
                    obj[key] = Converter.obj_to_dict(val)
            return obj
        else:
            return obj


class LogProxy:
    log_proxys = {}

    @classmethod
    def get_log_proxy(cls, log, file_name):
        if file_name:
            els = str(file_name).split(':')

            """Remove the prefix and suffix of the name of pipeline database"""
            if els[0] == 'pip-db':
                del els[0]
                del els[len(els) - 1]

            file_name = '_'.join(els)

            if file_name in cls.log_proxys.keys():
                return cls.log_proxys[file_name]
            else:
                log_proxy = LogProxy(log, file_name=file_name)
                cls.log_proxys[file_name] = log_proxy
                return log_proxy

    def __init__(self, log, file_name=None, level=logging.INFO):
        if log and isinstance(log, logging.Logger):
            self.log = log
            self.level = level
        else:
            self.log = None

        self.file_name = file_name
        self.log_file = None

    def write(self, p_str):
        if self.log:
            self.log.info('LogProxy$>' + p_str)
        else:
            print p_str
        self.do_log(logging.INFO, p_str)

    def error(self, msg, *args, **kwargs):
        if self.log:
            self.log.info(msg % args, **kwargs)
        self.do_log(logging.INFO, msg, *args)

    def info(self, msg, *args, **kwargs):
        if self.log:
            self.log.info(msg % args, **kwargs)
        self.do_log(logging.INFO, msg, *args)

    def flush(self):
        pass

    def do_up(self):
        if self.file_name:
            if not os.path.exists(BeetleConfig.PIPELINE_DB_LOG_PATH):
                commands.getoutput(
                    'mkdir -p %s' % BeetleConfig.PIPELINE_DB_LOG_PATH)

            log_file_name = '%s/%s.log' % (
            BeetleConfig.PIPELINE_DB_LOG_PATH, self.file_name)
            _log.info('start open database logfile : %s', log_file_name)
            self.log_file = open(log_file_name, 'w')
            _log.info('end open database logfile : %s', log_file_name)

    def do_down(self):
        if self.log_file:
            try:
                self.log_file.close()
            except IOError:
                pass

    def do_log(self, level, msg, *args):
        if self.log_file:
            if level == logging.ERROR:
                level = 'ERROR'
            elif level == logging.DEBUG:
                level = 'DEBUG'
            else:
                level = 'INFO'

            _time = time.strftime('%Y-%m-%d %H:%M:%S',
                                  time.localtime(time.time()))
            if args:
                self.log_file.write((str(_time) + ' [' + str(
                    level) + '] ' + msg + '\n') % args)
            else:
                self.log_file.write(
                    str(_time) + ' [' + str(level) + '] ' + msg + '\n')
            self.log_file.flush()


def _async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""
    if not inspect.isclass(exctype):
        raise TypeError("Only types can be raised (not instances)")
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid,
                                                     ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
        raise SystemError("PyThreadState_SetAsyncExc failed")


class TerminatableThread(threading.Thread):
    def _get_my_tid(self):
        """determines this (self's) thread id"""
        if not self.isAlive():
            raise threading.ThreadError("the thread is not active")
        # do we have it cached?
        if hasattr(self, "_thread_id"):
            return self._thread_id
        # no, look for it in the _active dict
        for tid, tobj in threading._active.items():
            if tobj is self:
                self._thread_id = tid
                return tid
        raise AssertionError("could not determine the thread's id")

    def raise_exc(self, exctype):
        """raises the given exception type in the context of this thread"""
        _async_raise(self._get_my_tid(), exctype)

    def terminate(self):
        """raises SystemExit in the context of the given thread, which should
        cause the thread to exit silently (unless caught)"""
        self.raise_exc(SystemExit)


def async(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        _log.info("before %s called." % func.__name__)
        call_thread = TerminatableThread(target=func, args=args, kwargs=kwargs)
        call_thread.daemon = True
        call_thread.start()
        call_thread.join(timeout=5)
        if call_thread.is_alive():
            _log.warn("call %s timeout." % func.__name__)
            call_thread.terminate()
        _log.info("after %s called." % func.__name__)

    return wrapper


class FileUtil:
    @staticmethod
    def file_exists(path):
        return os.path.isfile(path)

    @staticmethod
    def findfiles(dirname, pattern):
        cwd = os.getcwd()  # 保存当前工作目录
        if dirname:
            os.chdir(dirname)

        result = []
        for filename in glob.iglob("*"):  # 此处可以用glob.glob(pattern) 返回所有结果
            if re.search(pattern, filename, re.S | re.M):
                result.append(filename)
        # 恢复工作目录
        os.chdir(cwd)
        return result

    @staticmethod
    def delete(filepath):
        if (os.path.isfile(filepath)):
            os.remove(filepath)

    @staticmethod
    def cp(frompath, topath):
        copyfile(frompath, topath)



